﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using LuxuryResort.Entity;

namespace LuxuryResort.DAO
{
    /// <summary>
    ///     In memory IObjectSet. This class is intended only
    ///     for testing purposes.
    /// </summary>
    /// <typeparam name="TEntity">Type of elements in objectSet</typeparam>
    public sealed class InMemoryObjectSet<TEntity> : IObjectSet<TEntity>
        where TEntity : class
    {
        #region Members

        private readonly List<string> _includePaths;
        private readonly List<TEntity> _innerList;

        #endregion

        #region Constructor

        /// <summary>
        ///     Default constructor
        /// </summary>
        /// <param name="innerList">A List{T} with inner values of this IObjectSet</param>
        public InMemoryObjectSet(List<TEntity> innerList)
        {
            if (innerList == null)
                throw new ArgumentNullException("innerList");

            _innerList = innerList;
            _includePaths = new List<string>();
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Include path in query objects
        /// </summary>
        /// <param name="path">Path to include</param>
        /// <returns>IObjectSet with include path</returns>
        public InMemoryObjectSet<TEntity> Include(string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            _includePaths.Add(path);

            return this;
        }

        #endregion

        #region IObjectSet<T> Members

        /// <summary>
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </summary>
        /// <param name="entity">
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </param>
        public void AddObject(TEntity entity)
        {
            if (entity != null)
                _innerList.Add(entity);
        }

        /// <summary>
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </summary>
        /// <param name="entity">
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </param>
        public void Attach(TEntity entity)
        {
            if (entity != null
                &&
                !_innerList.Contains(entity))
            {
                _innerList.Add(entity);
            }
        }

        /// <summary>
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </summary>
        /// <param name="entity">
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </param>
        public void Detach(TEntity entity)
        {
            if (entity != null)
                _innerList.Remove(entity);
        }

        /// <summary>
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </summary>
        /// <param name="entity">
        ///     <see cref="System.Data.Objects.IObjectSet{T}" />
        /// </param>
        public void DeleteObject(TEntity entity)
        {
            if (entity != null)
                _innerList.Remove(entity);
        }

        #endregion

        #region IEnumerable<T> Members

        /// <summary>
        ///     <see cref="System.Collections.IEnumerable.GetEnumerator" />
        /// </summary>
        /// <returns>
        ///     <see cref="System.Collections.IEnumerable.GetEnumerator" />
        /// </returns>
        public IEnumerator<TEntity> GetEnumerator()
        {
            return ((IEnumerable<TEntity>) _innerList).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        ///     <see cref="System.Collections.IEnumerable.GetEnumerator" />
        /// </summary>
        /// <returns>
        ///     <see cref="System.Collections.IEnumerable.GetEnumerator" />
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IQueryable Members

        /// <summary>
        ///     <see cref="System.Linq.IQueryable{T}" />
        /// </summary>
        public Type ElementType
        {
            get { return typeof (TEntity); }
        }

        /// <summary>
        ///     <see cref="System.Linq.IQueryable{T}" />
        /// </summary>
        public Expression Expression
        {
            get { return _innerList.AsQueryable().Expression; }
        }

        /// <summary>
        ///     <see cref="System.Linq.IQueryable{T}" />
        /// </summary>
        public IQueryProvider Provider
        {
            get { return _innerList.AsQueryable().Provider; }
        }

        #endregion
    }

    public static class QueryableExtensions
    {
        /// <summary>
        ///     Include method for IQueryable
        /// </summary>
        /// <typeparam name="T">Type of elements</typeparam>
        /// <param name="queryable">Query able object</param>
        /// <param name="path">Path to include</param>
        /// <returns>Query able object with include path information</returns>
        public static IQueryable<T> Include<T>(this IQueryable<T> queryable, string path)
            where T : class
        {
            var query = queryable as ObjectQuery<T>;

            if (query != null) //if is a EF ObjectQuery object
                return query.Include(path);
            //a fake or in memory object set for testing
            var fakeQuery = queryable as InMemoryObjectSet<T>;
            return fakeQuery.Include(path);
        }


        /// <summary>
        ///     Include extension method for IQueryable
        ///     Strong typed INCLUDE clause
        /// </summary>
        public static IQueryable<T> Include<T>(this IQueryable<T> query, Expression<Func<T, object>> selector)
            where T : class, new()
        {
            string path = new PropertyPathVisitor().GetPropertyPath(selector);
            return Include(query, path);
        }

        /// <summary>
        ///     WhereIn extension using Parameter value
        /// </summary>
        /// <typeparam name="TElement"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="source"></param>
        /// <param name="propertySelector"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source,
            Expression<Func<TElement, TValue>> propertySelector, params TValue[] values)
        {
            return source.Where(GetWhereInExpression(propertySelector, values));
        }

        /// <summary>
        ///     WhereIn extension using Enumeration value
        /// </summary>
        /// <typeparam name="TElement"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="source"></param>
        /// <param name="propertySelector"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static IQueryable<TElement> WhereIn<TElement, TValue>(this IQueryable<TElement> source,
            Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
        {
            const int iLote = 500;
            IQueryable<TElement> queryOriginal = source;
            source = queryOriginal.Where(GetWhereInExpression(propertySelector, values.Take(iLote).ToList()));

            for (int i = iLote; i < values.Count(); i += iLote)
                source = source.Union(queryOriginal.WhereIn(propertySelector, values.Skip(i).Take(iLote).ToList()));

            return source;
        }

        private static Expression<Func<TElement, bool>> GetWhereInExpression<TElement, TValue>(
            Expression<Func<TElement, TValue>> propertySelector, IEnumerable<TValue> values)
        {
            ParameterExpression p = propertySelector.Parameters.Single();
            if (!values.Any())
                return e => false;

            IEnumerable<Expression> equals =
                values.Select(
                    value =>
                        (Expression)
                            Expression.Equal(propertySelector.Body, Expression.Constant(value, typeof (TValue))));
            Expression body = equals.Aggregate(Expression.Or);

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }
    }

    public class PropertyPathVisitor : ExpressionVisitor
    {
        private Stack<string> _stack;

        public string GetPropertyPath(Expression expression)
        {
            _stack = new Stack<string>();
            Visit(expression);
            return _stack
                .Aggregate(
                    new StringBuilder(),
                    (sb, name) =>
                        (sb.Length > 0 ? sb.Append(".") : sb).Append(name))
                .ToString();
        }

        protected override Expression VisitMethodCall(MethodCallExpression expression)
        {
            if (IsLinqOperator(expression.Method))
            {
                for (int i = 1; i < expression.Arguments.Count; i++)
                {
                    Visit(expression.Arguments[i]);
                }
                Visit(expression.Arguments[0]);
                return expression;
            }
            return base.VisitMethodCall(expression);
        }

        private static bool IsLinqOperator(MethodInfo method)
        {
            if (method == null) throw new ArgumentNullException("method");
            if (method.DeclaringType != typeof (Queryable) && method.DeclaringType != typeof (Enumerable))
                return false;
            return Attribute.GetCustomAttribute(method, typeof (ExtensionAttribute)) != null;
        }
    }

    public class BaseDAO
    {
        private readonly string _connectionString;
        private AuthenticationDBEntities _authenticationContext;
        private LRMPEntities _context;


        public BaseDAO(string connectionString)
        {
            _connectionString = connectionString;
        }

        public BaseDAO()
        {
            _connectionString = ConfigurationManager.ConnectionStrings["LRMPEntities"].ConnectionString;
        }

        public LRMPEntities LRMPContext
        {
            get { return _context ?? (_context = new LRMPEntities(_connectionString)); }
        }

        public AuthenticationDBEntities AuthenticationContext
        {
            get { return _authenticationContext ?? (_authenticationContext = new AuthenticationDBEntities()); }
        }

        protected List<LRMPConnection> GetConnectionString(Guid userId, Guid resortId)
        {
            try
            {
                var connections = new List<LRMPConnection>();
                var connectionStrings = new List<string>();
                using (var authenticationContext = new AuthenticationDBEntities())
                {
                    IQueryable<vw_aspnet_UsersInRoles> roles =
                        authenticationContext.vw_aspnet_UsersInRoles.Where(u => u.UserId == userId);
                    foreach (vw_aspnet_UsersInRoles role in roles)
                    {
                        Guid roleId = role.RoleId;
                        if (!resortId.Equals(new Guid()))
                        {
                            RoleDB connection =
                                authenticationContext.RoleDBs.FirstOrDefault(
                                    r => r.RoleId == roleId && r.ResortId == resortId);
                            Resort firstOrDefault =
                                authenticationContext.Resorts.FirstOrDefault(resort => resort.RID == connection.ResortId);
                            if (firstOrDefault != null)
                            {
                                string resortName =
                                    firstOrDefault
                                        .ResortName;

                                var entityConnectionStringBuilder = new EntityConnectionStringBuilder
                                {
                                    Provider = "System.Data.SqlClient",
                                    ProviderConnectionString = connection.ConnectionString,
                                    Metadata = "res://*"
                                };

                                if (!connectionStrings.Contains(connection.ConnectionString))
                                {
                                    connections.Add(new LRMPConnection
                                    {
                                        ResortName = resortName,
                                        ConnectionString = entityConnectionStringBuilder.ConnectionString
                                    });
                                    connectionStrings.Add(connection.ConnectionString);
                                }
                            }
                        }
                        else
                        {
                            List<RoleDB> lst = authenticationContext.RoleDBs.Where(r => r.RoleId == roleId).ToList();
                            foreach (RoleDB cn in lst)
                            {
                                Resort firstOrDefault =
                                    authenticationContext.Resorts.FirstOrDefault(r => r.RID == cn.ResortId);
                                if (firstOrDefault != null)
                                {
                                    string resortName =
                                        firstOrDefault
                                            .ResortName;
                                    var entityConnectionStringBuilder = new EntityConnectionStringBuilder
                                    {
                                        Provider = "System.Data.SqlClient",
                                        ProviderConnectionString = cn.ConnectionString,
                                        Metadata = "res://*"
                                    };

                                    if (!connectionStrings.Contains(cn.ConnectionString))
                                    {
                                        connections.Add(new LRMPConnection
                                        {
                                            ResortName = resortName,
                                            ConnectionString = entityConnectionStringBuilder.ConnectionString
                                        });
                                        connectionStrings.Add(cn.ConnectionString);
                                    }
                                }
                            }
                        }
                    }
                }

                return connections;
            }
            catch(TargetException ex)
            {
                throw ex;
            }
        }
    }

    public class LRMPConnection
    {
        public string ConnectionString { get; set; }
        public string ResortName { get; set; }
    }
}